using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using Confusing.Collections.Exceptions;

namespace Confusing.Collections
{
    public static class CollectionHelper
    {
        #region Fields
        
        #endregion

        #region Delegates
        public delegate int Compare<T>(T obj1, T obj2);
        #endregion

        public static TConcreteType MergeCollections<TInterfaceType, TConcreteType, TParameterType>(TInterfaceType one, TInterfaceType two)
            where TInterfaceType : ICollection<TParameterType>
            where TConcreteType : TInterfaceType, new()
        {
            TConcreteType result = new TConcreteType();

            bool addRangeSuccessful = false;

            try
            {
                Type concreteTypeType = result.GetType();
                MethodInfo method = concreteTypeType.GetMethod("AddRange");
                if (method != null)
                {
                    method.Invoke(result, new object[] { one });
                    method.Invoke(result, new object[] { two });
                    addRangeSuccessful = true;
                }
            }
            catch
            {
                addRangeSuccessful = false;
                result = default(TConcreteType);
            }


            if (!addRangeSuccessful)
            {
                if (one != null && one.Count > 0)
                {
                    foreach (TParameterType element in one)
                    {
                        result.Add(element);
                    }
                }
                if (two != null && two.Count > 0)
                {
                    foreach (TParameterType element in two)
                    {
                        result.Add(element);
                    }
                }
            }

            return result;
        }

        public static bool CollectionContains<TParameterType>(IComparer<TParameterType> comparer,
            ICollection<TParameterType> collectionToSearch, TParameterType objectToFind)
        {
            if (comparer == null)
            {
                throw new ArgumentNullException("comparer");
            }
            if (objectToFind == null)
            {
                throw new ArgumentNullException("objectToFind");
            }

            bool found = false;

            if (collectionToSearch.Count > 0)
            {
                foreach (TParameterType obj in collectionToSearch)
                {
                    if (comparer.Compare(objectToFind, obj) == 0)
                    {
                        found = true;
                        break;
                    }
                }
            }
            return found;
        }
        public static IDictionary<TKeyType, TValueType> ConstructMapFor<TKeyType, TValueType>(ICollection<TValueType> values, string propertyName)
        //where ValueType : class
        {
            if (values == null)
            {
                throw new ArgumentNullException("values");
            }
            if (propertyName == null)
            {
                throw new ArgumentNullException("propertyName");
            }

            //find the PropertyInfo with this propertyname
            Type valueType = typeof(TValueType);
            PropertyInfo pInfo = null;
            try
            {
                pInfo = valueType.GetProperty(propertyName);
            }
            catch (Exception)
            {
                throw new UnableToConstructMapException("Property error");
            }

            if (pInfo == null)
            {
                throw new PropertyNotFoundException(string.Format("Property [{0}] not found", propertyName));
            }

            if (!typeof(TKeyType).IsAssignableFrom(pInfo.PropertyType))
            {
                throw new UnexpectedTypeException("Type mismatch");
            }

            IDictionary<TKeyType, TValueType> resultDict = new Dictionary<TKeyType, TValueType>();

            foreach (TValueType value in values)
            {
                TKeyType keyValue = (TKeyType)pInfo.GetValue(value, null);
                resultDict.Add(keyValue, value);
            }


            return resultDict;
        }

        public static TParameterType FindInCollectionByProperty<TParameterType, TListType, TValueType>(TListType collection, TValueType valueToFind, string propertyName, Compare<TValueType> compareDelegate)
            where TListType : ICollection<TParameterType>
            where TParameterType : class
        {
            if (collection == null) { throw new ArgumentNullException("collection"); }
            if (valueToFind == null) { throw new ArgumentNullException("valueToFind"); }
            if (propertyName == null) { throw new ArgumentNullException("propertyName"); }
            if (compareDelegate == null) { throw new ArgumentNullException("compareDelegate"); }

            TParameterType foundObject = null;
            Type parameterTypeType = typeof(TParameterType);
            PropertyInfo pInfo = null;
            try
            {
                pInfo = parameterTypeType.GetProperty(propertyName);
            }
            catch (Exception)
            {
                throw new UnableToFindException("Property not found");
            }

            if (pInfo == null)
            {
                throw new UnableToFindException("Property not found");
            }

            if (!typeof(TValueType).IsAssignableFrom(valueToFind.GetType()))
            {
                throw new UnableToFindException("Type mismatch");
            }

            if (collection.Count > 0)
            {
                foreach (TParameterType value in collection)
                {
                    //get the value of the property
                    object propertyValueObj = pInfo.GetValue(value, null);
                    if (propertyValueObj != null)
                    {
                        TValueType currentValue = (TValueType)propertyValueObj;
                        if (compareDelegate(valueToFind, currentValue) == 0)
                        {
                            foundObject = value;
                            break;
                        }
                    }
                }
            }

            return foundObject;
        }

        public static TDestConcreteType ConvertCollection<TParameterType, TSourceCollectionType, TDestInterfaceType, TDestConcreteType>(TSourceCollectionType source)
            where TSourceCollectionType : ICollection<TParameterType>
            where TDestInterfaceType : ICollection<TParameterType>
            where TDestConcreteType : TDestInterfaceType, new()
        {
            if (source == null) { throw new ArgumentNullException("source"); }

            TDestConcreteType result = new TDestConcreteType();

            foreach (TParameterType param in source)
            {
                result.Add(param);
            }

            return result;
        }
        public static CollectionInterfaceType ChangeCollectionParamType<SourceParamType, DestParamType, CollectionSourceType, CollectionInterfaceType, CollectionConcreteType>(CollectionSourceType source)
            where SourceParamType : class
            where DestParamType : class//,SourceParamType
            where CollectionSourceType : ICollection<SourceParamType>
            where CollectionInterfaceType : ICollection<DestParamType>
            where CollectionConcreteType : CollectionInterfaceType, new()
        {
            if (source == null) { throw new ArgumentNullException("source"); }

            CollectionInterfaceType result = new CollectionConcreteType();

            foreach (SourceParamType sourceElement in source)
            {

                DestParamType destParam = null;
                try
                {
                    destParam = sourceElement as DestParamType;
                }
                catch (InvalidCastException ice)
                {
                    UnableToFindException ufe = new UnableToFindException("Type mismatch", ice);
                }

                if (sourceElement != null && destParam == null)
                {
                    throw new UnableToFindException("Type mismatch");
                }

                result.Add(destParam);
            }

            return result;
        }


        public static IList<TOutput> ConvertList<TInput, TOutput>(IList<TInput> listSource, Converter<TInput, TOutput> converter)
        {
            if (converter == null) { throw new ArgumentNullException("converter"); }

            IList<TOutput> outputList = new List<TOutput>();
            foreach (TInput input in listSource)
            {
                outputList.Add(converter(input));
            }
            return outputList;
        }


    }
}
